package com.zhiyuan.manager;

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 *
 *
 * @author zhiyuan
 * @date 2025/10/25
 */
public class TwoLevelCacheManager implements CacheManager {

    private final CacheManager l1Manager;
    private final CacheManager l2Manager;

    // 可选：用于广播失效（如传 null，则不广播）
    private final StringRedisTemplate stringRedisTemplate;
    private final String invalidateTopic;

    // 缓存 name -> TwoLevelCache
    private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>();

    public TwoLevelCacheManager(CacheManager l1Manager,
                                CacheManager l2Manager) {
        this(l1Manager, l2Manager, null, null);
    }

    public TwoLevelCacheManager(CacheManager l1Manager,
                                CacheManager l2Manager,
                                StringRedisTemplate stringRedisTemplate,
                                String invalidateTopic) {
        this.l1Manager = Objects.requireNonNull(l1Manager, "l1Manager (Caffeine) required");
        this.l2Manager = Objects.requireNonNull(l2Manager, "l2Manager (Redis) required");
        this.stringRedisTemplate = stringRedisTemplate;
        this.invalidateTopic = invalidateTopic;
    }

    @Override
    public Cache getCache(String name) {
        return cacheMap.computeIfAbsent(name, n -> {
            Cache l1 = l1Manager.getCache(n);
            Cache l2 = l2Manager.getCache(n);
            if (l1 == null) {
                throw new IllegalStateException("L1 cache not found for name: " + n);
            }
            if (l2 == null) {
                throw new IllegalStateException("L2 cache not found for name: " + n);
            }
            return new TwoLevelCache(n, l1, l2, this::publishInvalidate);
        });
    }

    @Override
    public Collection<String> getCacheNames() {
        Set<String> names = new LinkedHashSet<>();
        names.addAll(l1Manager.getCacheNames());
        names.addAll(l2Manager.getCacheNames());
        return Collections.unmodifiableSet(names);
    }

    /**
     * 由订阅失效消息的监听器调用：只清本机 L1，避免循环广播。
     */
    public void invalidateLocal(String cacheName, Object key) {
        Cache cache = cacheMap.get(cacheName);
        if (cache instanceof TwoLevelCache) {
            ((TwoLevelCache) cache).invalidateLocal(key);
        } else {
            Cache l1 = l1Manager.getCache(cacheName);
            if (l1 != null) {
                l1.evict(key);
            }
        }
    }

    /**
     * 同上，但对整个 cacheName 清本地 L1。
     */
    public void invalidateLocal(String cacheName) {
        Cache cache = cacheMap.get(cacheName);
        if (cache instanceof TwoLevelCache) {
            ((TwoLevelCache) cache).invalidateLocalAll();
        } else {
            Cache l1 = l1Manager.getCache(cacheName);
            if (l1 != null) {
                l1.clear();
            }
        }
    }

    /** 发布失效消息（cacheName::key 或 cacheName::* 表示全清）。 */
    private void publishInvalidate(String cacheName, Object keyOrWildcard) {
        if (stringRedisTemplate == null || invalidateTopic == null) return;
        String payload;
        if (keyOrWildcard == null) {
            payload = cacheName + "::*";
        } else {
            payload = cacheName + "::" + String.valueOf(keyOrWildcard);
        }
        try {
            stringRedisTemplate.convertAndSend(invalidateTopic, payload);
        } catch (Exception ignore) {
            // 广播失败不应影响主流程
        }
    }
}